

#ifndef __CORTISOL_VIDEOYUV_H
#define __CORTISOL_VIDEOYUV_H

#include "IVideoDecoder.h"

#include<Windows.h>

#define WITH_STATS_YUV 1

class VideoRAW
	:	public IVideoDecoder
{
	AVFormatContext *formatCtx;
	AVCodec			*rgbCodec;
	AVCodec			*alphaCodec;
	int				rgbStream;
	int				alphaStream;
	int				times;
	HANDLE			mutex;		/* decode frame mutex  */

	struct ReadFrameResult {
		enum e {

			EndOfVideo		= 0x00,
			VideoFrameRGB	= 0x02,
			VideoFrameAlpha	= 0x04,
			IgnoredFrame	= 0x08,
		};
	};

protected:

	std::string		file;
	AVCodecContext  *rgbCodecCtx;
	AVCodecContext  *alphaCodecCtx;
	AVFrame			*rgbNativeFrame;
	AVFrame			*alphaNativeFrame;

public:

#if(WITH_STATS_YUV)
	static HANDLE       statsMutex;
	static unsigned int totalFramesDecoded;
	static unsigned int totalVideosDecoded;
	static signed   int inProgress;
#endif /*** WITH_STATS_YUV ***/

	VideoRAW(const std::string &file, int times)
		:	file(file),
			formatCtx(NULL),
			rgbCodecCtx(NULL),
			alphaCodecCtx(NULL),
			rgbCodec(NULL),
			alphaCodec(NULL),
			rgbNativeFrame(NULL),
			alphaNativeFrame(NULL),
			rgbStream(-1),
			alphaStream(-1),
			times(times),
			mutex(NULL)
	{
		Initialise();
	}


	virtual ~VideoRAW() {

		CleanUp();
	}

	/*** returns false when there is no more work to be done ***/
	bool DecodeFrame() {

		DWORD lockState = WaitForSingleObject( mutex, 0 );

		switch(lockState)
		{
		case WAIT_OBJECT_0: /*** GOT THE MUTEX, OBJECT IS CLEAN ***/
		{
			#if(WITH_STATS_YUV)
				WaitForSingleObject(statsMutex, INFINITE);
				++inProgress;
				ReleaseMutex(statsMutex);
			#endif

			bool result = DecodeScene( false );
		
			#if(WITH_STATS_YUV)
				WaitForSingleObject(statsMutex, INFINITE);
				--inProgress;
				ReleaseMutex(statsMutex);
			#endif

			ReleaseMutex( mutex );
			return result; 
		}
		case WAIT_TIMEOUT: /*** DIDNT GET THE MUTEX ***/
			return true;  // try again later probably more work to do.

		case WAIT_ABANDONED: /*** GOT THE MUTEX, BUT PREV OWNER DIED DURING CRITICAL SECTION !!! ***/
			ReleaseMutex( mutex );

		default:
		case WAIT_FAILED: /*** GENERAL ERROR ***/
			throw Exception(std::string("Video Update mutex error in ") + file);
		}
	}

	bool HasAlphaChannel(void) const {

		return alphaCodec ? true : false;
	}

	int GetWidth(void) const {

		return rgbCodecCtx->width;
	}

	int GetHeight(void) const {

		return rgbCodecCtx->height;
	}

	long long PerformaneCounter(void)
	{
		LARGE_INTEGER li;
		if(QueryPerformanceCounter(&li)) {

			long long ll;
			ll = (li.u.HighPart);
			ll <<=32;
			ll |= li.u.LowPart;
			
			return ll;
		}


		return 0;
	}

protected:

	

	virtual void NotifyFrameDecoded() {
		return;
	}

private:

	bool DecodeScene(bool rewindFlag = false) {
		
		int framesRequired = alphaCodec ? 2 : 1;
		int flagsExpected = (int)ReadFrameResult::VideoFrameRGB | (int)(alphaCodec ? ReadFrameResult::VideoFrameAlpha : 0);
		int flagsGot = 0;
		
		long long basepc = 0;
		long long rawpc = 0;
		long long procpc = 0;

		basepc = PerformaneCounter();

		while(framesRequired) {
			
			ReadFrameResult::e result = ReadFrame();

			switch( result )
			{
			case ReadFrameResult::EndOfVideo:

				if(flagsGot != 0)
					throw Exception(std::string("unexpected end of video in ") + file);

				if(rewindFlag)
					throw Exception(std::string("zero length video in ") + file);

				#if(WITH_STATS_YUV)
					++totalVideosDecoded;
				#endif

				if(times-- > 0)
				{
					AVLib::Format()->av_seek_frame(formatCtx, rgbStream, 0, 0);
					if(alphaCodec)
						AVLib::Format()->av_seek_frame(formatCtx, alphaStream, 0, 0);

					return DecodeScene( true ); // LOOP
				}

				return false; // FINISHED !

			case ReadFrameResult::VideoFrameRGB:
			case ReadFrameResult::VideoFrameAlpha:

				framesRequired--;
				flagsGot |= (int)result;

			case ReadFrameResult::IgnoredFrame:
				break;
			}
		}

		if(flagsGot != flagsExpected)
			throw Exception(std::string("unexpected frames in ") + file);

		rawpc = PerformaneCounter() - basepc;

		NotifyFrameDecoded();

		procpc = (PerformaneCounter() - basepc) - rawpc;

		#if(WITH_STATS_YUV)
			++totalFramesDecoded;
		#endif

		return true;
	}

	ReadFrameResult::e ReadFrame() {

		AVPacket packet;

		if(AVLib::Format()->av_read_frame( formatCtx, &packet ) < 0)
			return ReadFrameResult::EndOfVideo;

		ReadFrameResult::e ret = DecodeFrame(&packet);

		AVLib::Codec()->av_free_packet(&packet);

		return ret;
	}

	ReadFrameResult::e DecodeFrame(AVPacket *packet) {

		if(packet->stream_index == rgbStream) {

			DecodeFrame( rgbCodecCtx, rgbNativeFrame, packet);
			return ReadFrameResult::VideoFrameRGB;
		}

		if(alphaCodec && packet->stream_index == alphaStream) {

			DecodeFrame( alphaCodecCtx, alphaNativeFrame, packet);
			return ReadFrameResult::VideoFrameAlpha;
		}

		return ReadFrameResult::IgnoredFrame;
	}

	void DecodeFrame(AVCodecContext *codecCtx, AVFrame *frame, AVPacket *packet) {

		int success;

		if(AVLib::Codec()->avcodec_decode_video(codecCtx, frame, &success, packet->data, packet->size) < 0)
			throw Exception(std::string("avcodec_decode_video error in ") + file);

		if(!success)
			throw  Exception(std::string("avcodec_decode_video no success in ") + file);
	}


	void Initialise() {

		mutex	 = CreateMutex(NULL,FALSE,NULL);

		if(AVLib::Format()->av_open_input_file(&formatCtx,file.c_str(),NULL,0,NULL) != 0)
			throw Exception(std::string("av_open_input_file in ") + file);

		if(AVLib::Format()->av_find_stream_info(formatCtx) < 0)
			throw Exception(std::string("av_find_stream_info in ") + file);

		for(unsigned int i=0; i<formatCtx->nb_streams; i++)
			if( formatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) {
				if(rgbStream==-1)
					rgbStream = (int)i;
				else
					alphaStream = (int)i;
			}
		if(rgbStream == -1)
			throw Exception(std::string("no video streams in ") + file);

		if(rgbStream != -1)
			rgbCodecCtx = formatCtx->streams[rgbStream]->codec;

		if(alphaStream != -1)
			alphaCodecCtx = formatCtx->streams[alphaStream]->codec;

		if((rgbCodec=AVLib::Codec()->avcodec_find_decoder(rgbCodecCtx->codec_id))==NULL)
			throw Exception(std::string("rgbCodec=avcodec_find_decoder() in ") + file);

		if(alphaCodecCtx && ((alphaCodec = AVLib::Codec()->avcodec_find_decoder(alphaCodecCtx->codec_id))==NULL))
			throw Exception(std::string("alphaCodec=avcodec_find_decoder() in ") + file);

		if(AVLib::Codec()->avcodec_open(rgbCodecCtx, rgbCodec) < 0)
			throw Exception(std::string("avcodec_open rgb in ") + file);

		if(alphaCodecCtx && (AVLib::Codec()->avcodec_open(alphaCodecCtx, alphaCodec) < 0))
			throw Exception(std::string("avcodec_open alpha in ") + file);

		if(!(rgbNativeFrame = AVLib::Codec()->avcodec_alloc_frame()))
			throw Exception(std::string("avcodec_alloc_frame rgb in ") + file);

		if(alphaCodec && !(alphaNativeFrame = AVLib::Codec()->avcodec_alloc_frame()))
			throw Exception(std::string("avcodec_alloc_frame alpha in ") + file);
	}

	void CleanUp() {

		WaitForSingleObject( mutex, INFINITE );

		if(rgbNativeFrame)
			AVLib::Util()->av_free(rgbNativeFrame);
		if(alphaNativeFrame)
			AVLib::Util()->av_free(alphaNativeFrame);
		if(rgbCodecCtx)
			AVLib::Codec()->avcodec_close(rgbCodecCtx);
		if(alphaCodecCtx)
			AVLib::Codec()->avcodec_close(alphaCodecCtx);
		if(formatCtx)
			AVLib::Format()->av_close_input_file(formatCtx);

		CloseHandle( mutex );
	}

};

#endif /*** __CORTISOL_VIDEOYUV_H ***/

